package com.rizzo.analytics.services.web.controller.v1;

import com.mangofactory.swagger.annotations.ApiErrors;
import com.mangofactory.swagger.annotations.ApiModel;
import com.mangofactory.swagger.annotations.ListType;
import com.rizzo.analytics.domain.account.Account;
import com.rizzo.analytics.domain.trigger.Trigger;
import com.rizzo.analytics.services.web.controller.v1.error.AccountNotFoundException;
import com.rizzo.analytics.services.web.controller.v1.error.DuplicateEntityException;
import com.rizzo.analytics.services.web.controller.v1.error.MalformedRequestException;
import com.rizzo.analytics.services.web.controller.v1.error.TriggerNotFoundException;
import com.rizzo.analytics.services.web.delegator.AccountDelegator;
import com.rizzo.infrastructure.dark.DarkLaunch;
import com.rizzo.infrastructure.performance.Monitor;
import com.rizzo.infrastructure.quota.Quota;
import com.rizzo.infrastructure.web.validator.Save;
import com.rizzo.infrastructure.web.validator.Update;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * All the HTTP endpoint account operations including those of triggers.
 * NOTE: once the user is logged in that we do not make a distinction between access-denied and not-found
 * exceptions since we don't want to give the violating the knowledge that the account exists. So a user
 * that does not have the permission to retrieve a certain account (and its triggers) will get a not-found
 * exception (nevertheless can it exist).
 */
//@Controller("accountController")
@RequestMapping(value = "/account", produces = "application/json")
@Api(value = "/account", description = "Operations about accounts.")
public class AccountController extends RizzoController {

    @Autowired
    private AccountDelegator accountDelegator;

    @Monitor(userBound = true)
    @PreAuthorize("hasRole('" + Account.ROLE_ADMIN + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "get-accounts")
    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, produces = "application/json")
    @ApiOperation(value = "List all available accounts",
            notes = "Return all the available accounts in the system. " +
                    "This endpoint can only be called when in role 'admin'!",
            responseClass = "org.springframework.data.domain.Page", tags = "account")
    @ApiModel(type = Account.class, collection = true, listType = ListType.List)
    public List<Account> getAccounts() {
        String user = getUsernameFromContext();
        return accountDelegator.getAccounts(user);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasRole('" + Account.ROLE_ADMIN + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "save-account")
    @ResponseBody
    @RequestMapping(method = RequestMethod.POST, produces = "application/json")
    @ApiOperation(value = "Save an account",
            notes = "This endpoint can only be called when in role 'admin' and account cannot contain an id!", tags = "account,save")
    @ApiErrors({MalformedRequestException.class, DuplicateEntityException.class})
    @ApiModel(type = Account.class, collection = false)
    public Account saveAccount(
            @RequestBody
            @Validated({Save.class})
            @ApiParam(name = "account", required = true,
                    value = "The account to save")
            @ApiModel(type = Account.class, collection = false) Account account) {
        String user = getUsernameFromContext();
        return accountDelegator.saveAccount(user, account);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasRole('" + Account.ROLE_ADMIN + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "update-account")
    @ResponseBody
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT, produces = "application/json")
    @ApiOperation(value = "Update an account",
            notes = "This endpoint can only be called when in role 'admin' and account must contain an id!", tags = "account,update")
    @ApiErrors({AccountNotFoundException.class, MalformedRequestException.class})
    @ApiModel(type = Account.class, collection = false)
    public Account updateAccount(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account to update") Long id,
            @RequestBody
            @Validated({Update.class})
            @ApiParam(name = "account", required = true,
                    value = "The account to update")
            @ApiModel(type = Account.class, collection = false) Account account) {
        String user = getUsernameFromContext();
        account.setId(id);
        return accountDelegator.updateAccount(user, account);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasRole('" + Account.ROLE_ADMIN + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "delete-accounts")
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "application/json")
    @ApiOperation(value = "Delete an account",
            notes = "This endpoint can only be called when in role 'admin'! " +
                    "Be aware that a delete of the account results in a delete of its triggers", tags = "account,delete")
    @ApiErrors({AccountNotFoundException.class})
    public void deleteAccount(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account to delete") Long id) {
        String user = getUsernameFromContext();
        accountDelegator.deleteAccount(user, id);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasAnyRole('" + Account.ROLE_ADMIN + "','" + Account.ROLE_USER + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "get-account")
    @ResponseBody
    @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = "application/json")
    @ApiOperation(value = "Get a specific account",
            notes = "This endpoint can only be called with the owning username when in role 'user'! " +
                    "When in role 'admin' all accounts are viewable!",
            responseClass = "com.rizzo.analytics.domain.account.Account", tags = "account")
    @ApiErrors({AccountNotFoundException.class})
    @ApiModel(type = Account.class, collection = false)
    public Account getAccount(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account to retrieve") Long id) {
        String user = getUsernameFromContext();
        return accountDelegator.getAccount(user, id);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasAnyRole('" + Account.ROLE_ADMIN + "','" + Account.ROLE_USER + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "get-triggers")
    @ResponseBody
    @RequestMapping(value = "/{id}/trigger", method = RequestMethod.GET, produces = "application/json")
    @ApiOperation(value = "List all available triggers for an account",
            notes = "Return all the available triggers in the system for a given account. " +
                    "This endpoint can be called when in role 'admin' (all the triggers from all the accounts) " +
                    "or 'user' (only the owning triggers)!",
            responseClass = "com.rizzo.analytics.domain.trigger.Trigger", tags = "trigger")
    @ApiErrors({AccountNotFoundException.class})
    @ApiModel(type = Trigger.class, collection = true, listType = ListType.List)
    public List<Trigger> getTriggers(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account for which the triggers are to be retrieved") Long id) {
        String user = getUsernameFromContext();
        return accountDelegator.getTriggers(user, id);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasAnyRole('" + Account.ROLE_ADMIN + "','" + Account.ROLE_USER + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "get-trigger")
    @ResponseBody
    @RequestMapping(value = "/{id}/trigger/{triggerId}", method = RequestMethod.GET, produces = "application/json")
    @ApiOperation(value = "Get a specific trigger for an account",
            notes = "Returns the specific trigger in the system for a given account. " +
                    "This endpoint can be called when in role 'admin' (all the triggers from all the accounts) " +
                    "or 'user' (only the owning triggers)!",
            responseClass = "com.rizzo.analytics.domain.trigger.Trigger", tags = "trigger")
    @ApiErrors({AccountNotFoundException.class, TriggerNotFoundException.class})
    @ApiModel(type = Trigger.class, collection = false)
    public Trigger getTrigger(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account for which the trigger is to be retrieved") Long id,
            @NotNull
            @PathVariable(value = "triggerId")
            @ApiParam(name = "triggerId", required = true,
                    value = "The id of the trigger that is to be retrieved") Long triggerId) {
        String user = getUsernameFromContext();
        return accountDelegator.getTrigger(user, id, triggerId);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasAnyRole('" + Account.ROLE_ADMIN + "','" + Account.ROLE_USER + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "save-trigger")
    @ResponseBody
    @RequestMapping(value = "/{id}/trigger", method = RequestMethod.POST, produces = "application/json")
    @ApiOperation(value = "Save a trigger for an account",
            notes = "Saves a trigger in the system for a given account (trigger cannot contain an id). " +
                    "This endpoint can be called when in role 'admin' (add trigger to all the accounts) " +
                    "or 'user' (only adding triggers to own account)!", tags = "trigger,save")
    @ApiErrors({AccountNotFoundException.class, TriggerNotFoundException.class})
    @ApiModel(type = Trigger.class, collection = false)
    public Trigger saveTrigger(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account for which the trigger is to be saved") Long id,
            @RequestBody
            @Validated({Save.class})
            @ApiParam(name = "trigger", required = true,
                    value = "The trigger to save")
            @ApiModel(type = Trigger.class, collection = false) Trigger trigger) {
        String user = getUsernameFromContext();
        return accountDelegator.saveTrigger(user, id, trigger);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasAnyRole('" + Account.ROLE_ADMIN + "','" + Account.ROLE_USER + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "update-trigger")
    @RequestMapping(value = "/{id}/trigger/{triggerId}", method = RequestMethod.PUT, produces = "application/json")
    @ApiOperation(value = "Update a specific trigger for an account",
            notes = "Updates a trigger in the system for a given account (trigger must have an id). " +
                    "This endpoint can be called when in role 'admin' (update trigger from all the accounts) " +
                    "or 'user' (only updating triggers of own account)!", tags = "trigger,update")
    @ApiErrors({AccountNotFoundException.class, TriggerNotFoundException.class})
    @ApiModel(type = Trigger.class, collection = false)
    public Trigger updateTrigger(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account for which the trigger is to be updated") Long id,
            @NotNull
            @PathVariable(value = "triggerId")
            @ApiParam(name = "triggerId", required = true,
                    value = "The id of the trigger that is to be updated") Long triggerId,
            @RequestBody
            @Validated({Update.class})
            @ApiParam(name = "trigger", required = true,
                    value = "The trigger to update")
            @ApiModel(type = Trigger.class, collection = false) Trigger trigger) {
        String user = getUsernameFromContext();
        trigger.setId(triggerId);
        return accountDelegator.updateTrigger(user, id, trigger);
    }

    @Monitor(userBound = true)
    @PreAuthorize("hasAnyRole('" + Account.ROLE_ADMIN + "','" + Account.ROLE_USER + "')")
    @Quota(maxCalls = 100, expireSeconds = 30)
    @DarkLaunch(name = "delete-trigger")
    @RequestMapping(value = "/{id}/trigger/{triggerId}", method = RequestMethod.DELETE, produces = "application/json")
    @ApiOperation(value = "Delete a specific trigger of an account",
            notes = "Deletes the specific trigger in the system for a given account. " +
                    "This endpoint can be called when in role 'admin' (delete all the triggers from all the accounts) " +
                    "or 'user' (only the owning triggers can be deleted)!",
            responseClass = "com.rizzo.analytics.domain.trigger.Trigger", tags = "trigger")
    @ApiErrors({AccountNotFoundException.class, TriggerNotFoundException.class})
    public void deleteTrigger(
            @NotNull
            @PathVariable(value = "id")
            @ApiParam(name = "id", required = true,
                    value = "The id of the account for which the trigger is to be deleted") Long id,
            @NotNull
            @PathVariable(value = "triggerId")
            @ApiParam(name = "triggerId", required = true,
                    value = "The id of the trigger that is to be deleted") Long triggerId) {
        String user = getUsernameFromContext();
        accountDelegator.deleteTrigger(user, id, triggerId);
    }
}